/*
 * Copyright 2011 Andres.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package mx.uacam.balam.notorrent.seeder;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ConnectException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import mx.uacam.balam.entities.Descriptor;
import mx.uacam.balam.entities.File;
import mx.uacam.balam.entities.Part;
import mx.uacam.balam.entities.SeederDetails;

/**
 *
 * @author Freddy
 * @author Andres
 */
public class Seeder implements Runnable {

    private String name;
    private String address;
    private String trackerAddress;
    private int portListenClient;
    private int portListenTracker;
    private int trackerPort;
    private List<File> dictionary;
    private int status;
    private boolean detener;
    public static int ALIVE = 1;
    public static int OCCUPIED = 0;
    private java.io.File userHome;
    private java.io.File seederFolder;
    private java.io.File partsFolder;
    private java.io.File shareFolder;

    /**
     * 
     * @param name
     * @param address
     * @param portListenClient
     * @param trackerAddress
     * @param trackerPort
     * @param portListenTracker 
     */
    public Seeder(String name, String address, int portListenClient, String trackerAddress, int trackerPort, int portListenTracker) {
        this.name = name;
        this.address = address;
        this.trackerAddress = trackerAddress;
        this.portListenClient = portListenClient;
        this.trackerPort = trackerPort;
        this.portListenTracker = portListenTracker;
        detener = false;
        dictionary = new ArrayList<File>();
        status = ALIVE;
        userHome = new java.io.File(System.getProperty("user.home", "."));
        seederFolder = new java.io.File(userHome, ".seeder" + name);
        partsFolder = new java.io.File(seederFolder, "parts");
        shareFolder = new java.io.File(seederFolder, "share");
    }

    @Override
    public void run() {
        checkFolder();
        notifyTracker();

        while (!detener) {
            try {
                byte[] buf = new byte[1024];
                DatagramSocket socket = new DatagramSocket(getPortListenClient());
                DatagramPacket dp = new DatagramPacket(buf, buf.length);

                System.out.println("Seeder is ready...");

                try {
                    socket.receive(dp);
                    String recibido = new String(dp.getData());
                    send(recibido, dp.getAddress().getHostName(), dp.getPort());

                } catch (IOException ex) {
                    Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
                }
            } catch (SocketException ex) {
                Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
            }
        }
    }

    public void stop() {
        detener = true;
    }

    public boolean findFile() {
       String fileName;
       File file = new File();
       byte[] buf = new      byte[1024];
       DatagramPacket dp = new  DatagramPacket(buf, buf.length);
        
            try {
           while (true){
                DatagramSocket socket = new DatagramSocket(2724);
                System.out.println("escuchando Datagramas...");
                socket.receive(dp);
                String rcvd = new String(dp.getData());
                fileName=rcvd;
                System.out.println(rcvd);
                
                file.setName(fileName);
            }
            } catch (IOException ex) {
                Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
            }
        
        

        return dictionary.contains(file);
    }

    public void send(String idNamePart, String address, int port) {
        final Part part = getPart(idNamePart);
        final int portSend = port;

        new Thread() {

            @Override
            public void run() {
                try {
              DatagramSocket socket = new DatagramSocket(portSend);
              DatagramPacket dp = new DatagramPacket(part.getBytes(), part.getBytes().length);

                    try {
                        socket.send(dp);
                        socket.close();
                    } catch (IOException ex) {
                        Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
                    }
                } catch (SocketException ex) {
                    Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
                }
            }
        };
    }

    public boolean upload(java.io.File file) {

        //TODO Implementar este metodo para que guarde un archivo en la carpeta del servidor
        java.io.File fileOutput = new java.io.File(shareFolder, file.getName());
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(file);

            int size = 0xf4240;// 1 000 000 bytes aprox 1 MB
            byte buffer[] = new byte[size];

            while (true) {
                int i = fis.read(buffer, 0, size);

                if (i == -1) {
                    break;
                }


                FileOutputStream fos = new FileOutputStream(fileOutput, true);
                fos.write(buffer);
                fos.flush();
                fos.close();
            }
        } catch (FileNotFoundException ex) {
            Logger.getLogger(FileSplitter.class.getName()).log(Level.SEVERE, null, ex);
        } catch (IOException ex) {
            Logger.getLogger(FileSplitter.class.getName()).log(Level.SEVERE, null, ex);
        } finally {
            try {
                fis.close();
            } catch (IOException ex) {
                Logger.getLogger(FileSplitter.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        checkFolder();
        return true;
    }

    private void checkFolder() {
        if (!seederFolder.exists()) {
            seederFolder.mkdir();
            shareFolder.mkdir();
            partsFolder.mkdir();
        } else {
            java.io.File[] listFiles = shareFolder.listFiles();

            for (int i = 0; i < listFiles.length; i++) {
                java.io.File file = listFiles[i];
                FileSplitter.split(file, partsFolder);
                File fileInfo = new File();
                fileInfo.setName(file.getName());
                fileInfo.setSize(file.length());
                List<Part> parts = new ArrayList<Part>();

                String[] fileNames = partsFolder.list();

                for (int j = 0; j < fileNames.length; j++) {
                    String fileName = fileNames[j];

                    String string = splitNamePart(fileName)[0];

                    if (string.equals(file.getName())) {
                        parts.add(getPart(fileName));
                    }
                }

                fileInfo.setParts(parts.toArray(new Part[0]));

                dictionary.add(fileInfo);
            }
        }
    }

    private void notifyTracker() {
        try {

            Socket clientTracker = new Socket(InetAddress.getByName(getTrackerAddress()), getTrackerPort());

            ObjectOutputStream salida = new ObjectOutputStream(clientTracker.getOutputStream());
            ObjectInputStream entrada = new ObjectInputStream(clientTracker.getInputStream());
            salida.flush();

            salida.writeObject(new SeederDetails(getName(), getPortListenClient(), getAddress(), getStatus(), getPortListenTracker()));          

            String mensaje;
            try {
               Object obj= entrada.readObject();
                  System.out.println(obj.toString());
                   if (obj.equals("confirmado")) {
                   findFile();
                 }


            } catch (ClassNotFoundException ex) {
                Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, null, ex);
            }
           
             
            salida.close();
            entrada.close();
            clientTracker.close();
            new SockectSearch().start();
        } catch (ConnectException ex) {
            Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
            stop();
        } catch (UnknownHostException ex) {
            Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
            stop();
        } catch (IOException ex) {
            Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
            stop();
        }
    }

    private Part getPart(String idPart) {
        String[] splitNamePart = splitNamePart(idPart);
        String namefile = splitNamePart[0];
        int id = Integer.parseInt(splitNamePart[1]);

        if (!findFile(/*namefile*/)) {

            Part part = null;
            try {
                java.io.File tmpFile = new java.io.File(partsFolder, idPart);

                FileInputStream fis = new FileInputStream(tmpFile);

                int size = 0xf4240;
                byte[] buffer = new byte[size];
                while (true) {

                    int i = 0;
                    try {
                        i = fis.read(buffer, 0, size);
                    } catch (IOException ex) {
                        Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
                    }

                    if (i == -1) {
                        break;
                    }
                }

                part = new Part(id, namefile, tmpFile.length(), buffer);

            } catch (FileNotFoundException ex) {
                Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);
            }

            return part;
        } else {
            Part part = null;
            for (File file : dictionary) {

                if (file.getName().equals(namefile)) {
                    part = file.getParts()[id];
                }
            }
            return part;
        }
    }

    private String[] splitNamePart(String fileName) {
        String[] split = fileName.split(".0");
        String string = split[0];
        String[] splitExt = split[1].split(".part");

        return new String[]{string, splitExt[0]};
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public int getPortListenClient() {
        return portListenClient;
    }

    public void setPortListenClient(int port) {
        this.portListenClient = port;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

    public int getPortListenTracker() {
        return portListenTracker;
    }

    public void setPortListenTracker(int portListeTracker) {
        this.portListenTracker = portListeTracker;
    }

    public String getTrackerAddress() {
        return trackerAddress;
    }

    public void setTrackerAddress(String tackerAddress) {
        this.trackerAddress = tackerAddress;
    }

    public int getTrackerPort() {
        return trackerPort;
    }

    public void setTrackerPort(int trackerPort) {
        this.trackerPort = trackerPort;
    }

    public List<File> getDictionary() {
        return dictionary;
    }

    private void setDictionary(List<File> dictionary) {
        this.dictionary = dictionary;
    }

    class SockectSearch extends Thread {

        private ServerSocket serverSocket;

        public SockectSearch() {
            try {
                serverSocket = new ServerSocket(portListenClient);
            } catch (IOException ex) {
                Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, null, ex);
            }
        }

        @Override
        public void run() {
            while (true) {
                try {
                    Socket accept = serverSocket.accept();
                    ObjectInputStream ois = new ObjectInputStream(accept.getInputStream());
                    try {
                        String fileName = (String) ois.readObject();
                        if (findFile(/*namefile*/)) {
                            Descriptor fd = new Descriptor();
                            for (File file : dictionary) {
                                if (file.getName().equals(fileName)) {

                                    fd.setFilename(fileName);
                                    fd.setNumParts(file.getParts().length);
                                    fd.setUrl(new String[]{getAddress()});
                                    fd.setSize(file.getSize());
                                }
                            }
                            ObjectOutputStream oos = new ObjectOutputStream(accept.getOutputStream());
                            oos.writeObject(fd);
                        }

                    } catch (ClassNotFoundException ex) {
                        Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, null, ex);
                    }
                } catch (IOException ex) {
                    Logger.getLogger(Seeder.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    }
}
